package com.lazyframework.commons.support.mp;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Locale;

/**
 * MyBatisPlus查询条件构造器
 *
 * <h2>Example Usage</h2>
 *
 * <pre>
 *  class UserQuery {
 *
 *      &#064;Conditional(value = ConditionalType.LIKE, column = "u1.username")
 *      private String username;
 *
 *      &#064;Conditional(value = ConditionalType.NE, column = "nickname")
 *      private String nickname;
 *
 *      &#064;Conditional(value = ConditionalType.EQ)
 *      private Integer userType;
 *
 *  }
 *
 *  class UserService {
 *
 *      public List<User> listAll(UserQuery query) {
 *          QueryWrapper<User> qw = QwHelper.buildQw(query);
 *          return list(qw);
 *      }
 *
 *  }
 * </pre>
 * Create by lazy in 2019/10/25
 */
@Slf4j
public class QwHelper {

    private static final String READ_METHOD_PREFIX = "get";

    /**
     * 根据传入的查询对象，构造MyBatisPlus查询条件
     *
     * @param query 查询对象 可以是任何普通的POJO对象
     * @return QueryWrapper
     */
    public static <T> QueryWrapper<T> buildQw(Object query) {
        QueryWrapper<T> qw = Wrappers.query();
        if (query == null) {
            return qw;
        }
        Class<?> clazz = query.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            Object value;
            try {
                value = getReadMethod(clazz, field.getName()).invoke(query);
            } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
                log.warn("{} has been ignored, because can't access it.", field.getName());
                continue;
            }

            if (value == null) {
                continue;
            }

            Conditional conditional = field.getDeclaredAnnotation(Conditional.class);
            if (conditional == null || conditional.ignore()) {
                continue;
            }

            String column, assignColumn = conditional.column();
            if (!StringUtils.isEmpty(assignColumn)) {
                column = assignColumn;
            } else {
                column = underscoreName(field.getName());
            }

            addFilter(qw, column, value, conditional.value());
        }
        return qw;
    }

    /**
     * 添加筛选条件
     *
     * @param qw     QueryWrapper
     * @param column 数据库列名
     * @param value  筛选值
     * @param cType  筛选方式
     */
    public static <T> void addFilter(QueryWrapper<T> qw, String column, Object value, ConditionalType cType) {
        switch (cType) {
            case EQ:
                qw.eq(column, value);
                break;
            case LE:
                qw.le(column, value);
                break;
            case GE:
                qw.ge(column, value);
                break;
            case LT:
                qw.lt(column, value);
                break;
            case GT:
                qw.gt(column, value);
                break;
            case IN:
                qw.in(column, value);
                break;
            case LIKE:
                qw.like(column, value);
                break;
            case IS_NULL:
                qw.isNull(column);
                break;
            case NOT_NULL:
                qw.isNotNull(column);
                break;
            case NE:
                qw.ne(column, value);
                break;
            case NOT_IN:
                qw.notIn(column, value);
                break;
        }
    }

    /**
     * 驼峰转下划线
     *
     * @param name
     * @return
     */
    protected static String underscoreName(String name) {
        if (!StringUtils.isNotEmpty(name)) {
            return "";
        }
        StringBuilder result = new StringBuilder();
        result.append(lowerCaseName(name.substring(0, 1)));
        for (int i = 1; i < name.length(); i++) {
            String s = name.substring(i, i + 1);
            String slc = lowerCaseName(s);
            if (!s.equals(slc)) {
                result.append("_").append(slc);
            } else {
                result.append(s);
            }
        }
        return result.toString();
    }

    protected static String lowerCaseName(String name) {
        return name.toLowerCase(Locale.US);
    }

    /**
     * 获取getter方法
     *
     * @param clazz
     * @param property
     * @return
     * @throws NoSuchMethodException
     */
    private static Method getReadMethod(Class<?> clazz, String property) throws NoSuchMethodException {
        String methodName = READ_METHOD_PREFIX + (property.substring(0, 1).toUpperCase() + property.substring(1));
        return clazz.getMethod(methodName);
    }

}
